// 《围棋》作者版权所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 内存分配器。
// 
// 这最初是基于tcmalloc的，但有很大的分歧。
// http:

// 主分配器在页面运行时工作。
// 小的分配大小（不超过32 kB）是
// 四舍五入到大约70个大小的类中的一个，每个类
// 都有自己的完全相同大小的自由对象集。
// 任何可用内存页都可以拆分为一组大小相同的对象
// 然后使用可用位图进行管理。
// 
// 分配器的数据结构是：
// 
// fixalloc:用于固定大小堆外对象的自由列表分配器，
// 用于管理分配器使用的存储。
// mheap:malloc堆，以页（8192字节）粒度管理。
// mspan：由mheap管理的正在使用的页面的运行。
// mcentral:收集给定大小类的所有跨度。
// mcache：具有可用空间的MSPAN的per-P缓存。
// mstats:分配统计信息。
// 
// 分配一个小对象将按照缓存的层次结构进行：
// 
// 1。将大小四舍五入到一个小大小类
// 中，并查看此P的mcache中相应的mspan。
// 扫描mspan的空闲位图以查找空闲插槽。
// 如果有空闲插槽，请分配它。
// 这一切都可以在不获取锁的情况下完成。中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网。如果mspan没有可用插槽，请从McCentral的具有所需大小
// 。
// 类且具有可用空间的mspan列表中获取新的mspan 
// 获得整个跨度将分摊锁定
// mcentral的成本。中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网。如果mcentral的mspan列表为空，请从mheap获取用于mspan的运行
// 页。中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译。如果mheap为空或没有足够大的页面运行，
// 从
// 操作系统分配一组新的页面（至少1MB）。分配大量页面
// 分摊与操作系统对话的成本。
// 
// 扫掠mspan并释放mspan上的对象将进入类似的
// 层次结构：
// 
// 1。如果mspan正在扫描以响应分配，则它
// 将返回给mcache以满足分配。中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网。否则，如果mspan中仍然有已分配的对象，
// 它将被放置在mspan大小
// 类的mcentral free列表中。中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网。否则，如果mspan中的所有对象都是空闲的，则mspan的
// 页面将返回给mheap，此时mspan已失效。
// 
// 分配和释放大型对象直接使用mheap 
// 绕过mcache和mcentral。
// 
// 如果mspan.needzero为false，则mspan中的可用对象插槽已归零。否则，如果needzero为true，则对象被归零为
// 它们已分配。延迟归零有多种好处：
// 
// 1。堆栈帧分配可以完全避免归零。中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译。它表现出更好的时间局部性，因为程序是
// 可能即将写入内存。中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网。我们没有零个永远不会被重用的页面。

// 虚拟内存布局
// 
// 堆由一组竞技场组成，64位为64MB，32位为
// 4MB（heapArenaBytes）。每个竞技场的起始地址也是
// 与竞技场大小一致。
// 
// 每个竞技场都有一个关联的heapArena对象，该对象存储该竞技场的
// 元数据：竞技场中所有单词的堆位图
// 以及竞技场中所有页面的跨距图。heapArena对象是
// 它们自己被分配到堆外。
// 
// 由于竞技场是对齐的，因此地址空间可以看作是
// 竞技场帧系列。
// 竞技场帧号为*heapArena，或零表示地址
// 未由Go堆支持的部分空间。竞技场地图结构为
// 两级数组，由一个“L1”竞技场地图和多个“L2”竞技场
// 地图组成；然而，由于竞技场很大，在许多体系结构上，
// 竞技场地图由一个较大的L2地图组成。
// 
// 竞技场地图覆盖了整个可能的地址空间，允许Go堆使用地址空间的任何部分。分配器
// 试图保持竞技场连续，以便大跨度（因此
// 大对象）可以跨竞技场。

package runtime

import (
	"runtime/internal/atomic"
	"runtime/internal/math"
	"runtime/internal/sys"
	"unsafe"
)

const (
	debugMalloc = false

	maxTinySize   = _TinySize
	tinySizeClass = _TinySizeClass
	maxSmallSize  = _MaxSmallSize

	pageShift = _PageShift
	pageSize  = _PageSize
	pageMask  = _PageMask
	// 通过构造，最小对象类的单页跨距
	// 每个跨距的对象数最多。
	maxObjsPerSpan = pageSize / 8

	concurrentSweep = _ConcurrentSweep

	_PageSize = 1 << _PageShift
	_PageMask = _PageSize - 1

	// \u 64位系统上为1，32位系统上为0 
	_64bit = 1 << (^uintptr(0) >> 63) / 2

	// Tiny分配器参数，请参阅malloc.go中的“Tiny分配器”注释。
	_TinySize      = 16
	_TinySizeClass = int8(2)

	_FixAllocChunk = 16 << 10 // FixAlloc的块大小

	// /Per-P、Per-order堆栈段缓存大小。
	_StackCacheSize = 32 * 1024

	// 获取缓存的订单数。订单0是固定的堆栈
	// 并且每个连续订单都是两倍大。
	// 我们希望缓存2KB、4KB、8KB和16KB堆栈。更大的堆栈
	// 将直接分配。
	// 由于FixedStack在不同的系统上是不同的，我们
	// 必须改变NumStackOrders以保持相同的最大缓存大小。
	// OS固定堆栈NumStackOrders 
	// ------------------++-------------------
	// linux/darwin/bsd | 2KB | 4 
	// windows/32 | 4KB | 3 
	// windows/64 | 8KB | 2 
	_NumStackOrders = 4 - sys.PtrSize/4*sys.GoosWindows - 1*sys.GoosPlan9

	// HeapadRbits是堆地址中的位数。在
	// amd64上，地址的符号扩展超出了HeapArbit。在
	// /其他拱门上，它们是零延伸的。
	// 
	// 在大多数64位平台上，基于硬件和操作系统限制的组合，我们将此限制为48位。
	// 
	// amd64硬件将地址限制为48位，符号扩展
	// 限制为64位。前16位不是
	// 全部0或全部1的地址为“非规范”且无效。由于
	// 这些“负”地址，在将索引计算到
	// （arenaBaseOffset）。2017年，amd64硬件增加了
	// 堆arenas索引之前，我们在amd64上将地址偏移1<<47 
	// 对57位地址的支持；但是，目前只有Linux 
	// 支持此扩展，内核将永远不会选择1<<47以上的
	// 地址，除非使用提示
	// 1<<47以上的地址调用mmap（我们从未这样做）。
	// 
	// arm64硬件（自ARMv8起）将用户地址限制为48个
	// 位，范围为[0，1<<48）.在硬件中，
	// 
	// ppc64、mips64和s390x支持任意64位地址
	// 在Linux上，Go依赖于更严格的操作系统限制。在Linux处理器.h上，基于
	// 的用户地址空间受到限制，如
	// 遵循64位体系结构：
	// 
	// 体系结构名称最大值（独占）
	// ---------------------------------------------------------------------------------------
	// /amd64任务大小最大0x007ffffffff000（47位地址）
	// arm64任务大小0x01000000000000（48位地址）
	// ppc64{，le}任务大小用户64 0x00400000000000（46位地址）
	// mips64{，le}任务大小64 0x0001000000000（40位地址）
	// s390x任务大小1<<64（64位地址）
	// 
	// 这些限制可能会随着时间的推移而增加，但目前处于
	// 除s390x之外的大多数48位。在所有体系结构上，Linux 
	// 开始将mmap区域放置在
	// 显著低于48位的地址，因此即使有可能3
	// 实践中是极不可能的。
	// 
	// 在32位平台上，我们接受完整的32位地址
	// 空间，因为这样做很便宜。
	// mips32只能访问低2GB的虚拟内存，所以
	// 我们进一步将其限制为31位。
	// 
	// 在ios/arm64上，尽管64位指针可能是
	// 可用，但指针被截断为33位。此外，
	// 对于应用程序来说，实际上只有地址空间的前4个GiB可用，但为了简单起见，我们还是允许全部33位。ABCFDG 
	// 
	// WebAssembly当前的线性内存限制为4GB。
	heapAddrBits = (_64bit*(1-sys.GoarchWasm)*(1-sys.GoosIos*sys.GoarchArm64))*48 + (1-_64bit+sys.GoarchWasm)*(32-(sys.GoarchMips+sys.GoarchMipsle)) + 33*sys.GoosIos*sys.GoarchArm64

	// maxAlloc是分配的最大大小。在64位上，
	// 理论上可以分配1个<heapdRbits字节。但是，在
	// 32位上，该值小于1<<32，因为地址空间中的字节数实际上不适合uintptr中的
	// 。
	maxAlloc = (1 << heapAddrBits) - (1-_64bit)*1

	// 堆地址中的位数、堆的大小
	// 竞技场以及L1和L2竞技场地图大小由
	// 
	// （1<<addr bits）=竞技场大小*L1条目*L2条目
	// 
	// 当前，我们平衡如下：
	// 
	// /平台地址位竞技场大小L1条目L2条目
	// /-----------------------------
	// /*/64位48 64MB 1 4M（32MB）
	// windows/64位48 4MB 64 1M（8MB）
	// /ios/arm64 33 4MB 1 2048（8KB）
	// /*/32位32 4MB 1 1024（4KB）
	// /*/mips（le）31 4MB 1 512（2KB）

	// heapArenaBytes是堆竞技场的大小。堆
	// 由大小为heapArenaBytes的映射组成，这些映射与
	// heapArenaBytes对齐。最初的堆映射是一个竞技场。
	// 
	// 当前在64位非Windows上为64MB，在
	// 32位和Windows上为4MB。我们在Windows上使用更小的竞技场，因为所有提交的内存都将计入进程，即使它不被触摸。因此，对于具有小
	// 堆的进程，映射的竞技场空间需要相称。
	// 这对于种族检测器
	// 尤其重要，因为它显著增加了承诺
	// 内存的成本。
	heapArenaBytes = 1 << logHeapArenaBytes

	// logHeapArenaBytes是heapArenaBytes的logu 2。为清楚起见，
	// 在可能的情况下更倾向于使用heapArenaBytes（我们需要
	// 常量来计算其他一些常量）。
	logHeapArenaBytes = (6+20)*(_64bit*(1-sys.GoosWindows)*(1-sys.GoarchWasm)*(1-sys.GoosIos*sys.GoarchArm64)) + (2+20)*(_64bit*sys.GoosWindows) + (2+20)*(1-_64bit) + (2+20)*sys.GoarchWasm + (2+20)*sys.GoosIos*sys.GoarchArm64

	// heapArenaBitmapBytes是每个堆竞技场位图的大小。
	heapArenaBitmapBytes = heapArenaBytes / (sys.PtrSize * 8 / 2)

	pagesPerArena = heapArenaBytes / pageSize

	// 竞技场1位是一级竞技场地图中竞技场编号
	// 的位数。
	// 
	// 这个数字应该很小，因为一级竞技场
	// 地图需要在
	// 二进制的BSS中使用PtrSize*（1<<arenaL1Bits）空间。它可以为零，在这种情况下，一级
	// 索引实际上未使用。这对性能有好处，因为生成的代码可能更高效，但代价是要有一个大的二级映射。
	// 
	// 我们在64位Windows上使用L1映射，因为竞技场大小
	// 很小，但地址空间仍然是48位，而
	// 拥有一个大型L2的成本很高。
	arenaL1Bits = 6 * (_64bit * sys.GoosWindows)

	// arenaL2Bits是竞技场编号
	// 包含在二级竞技场索引中的位数。
	// 
	// 每个竞技场地图分配的大小与
	// 1<<Arenal2位成比例，因此重要的是不要太大。48位导致32MB竞技场索引分配，这大约是实际阈值。
	arenaL2Bits = heapAddrBits - logHeapArenaBytes - arenaL1Bits

	// arenaL1Shift是将竞技场帧移位的位数
	// 将索引计算到一级竞技场地图的位数。
	arenaL1Shift = arenaL2Bits

	// arenaBits是组合竞技场地图索引中的总位。
	// 这被分成L1竞技场地图索引和
	// L2竞技场地图索引。
	arenaBits = arenaL1Bits + arenaL2Bits

	// arenaBaseOffset是与堆竞技场映射中的
	// 索引0相对应的指针值。在amd64上，地址空间为48位，符号扩展为64位。这个偏移量允许我们处理“负”地址（或
	// 高地址（如果视为未签名）。
	// 
	// 在aix/ppc64上，此偏移量允许将HeapArbits保留到
	// 48。否则，为了处理mmap地址
	// （范围0x0a00000000000000-0x0AFFFFFFFFFFFFFF），它将是60。但在这种情况下，（s*pageAlloc.init中为块
	// 保留的内存导致了严重的速度减慢。
	// 
	// 在其他平台上，用户地址空间是连续的
	// 并且从0开始，因此不需要偏移量。
	arenaBaseOffset = 0xffff800000000000*sys.GoarchAmd64 + 0x0a00000000000000*sys.GoosAix
	// 此常量的类型化版本，将使其成为DWARF（用于viewcore）。
	arenaBaseOffsetUintptr = uintptr(arenaBaseOffset)

	// 运行垃圾回收的最大线程数。
	// 2、3和4都是合理的最大值，具体取决于机器的硬件细节。垃圾
	// 收集器可以很好地扩展到32个CPU。
	_MaxGcproc = 32

	// minLegalPointer是最小的合法指针。
	// 这是可能的最小架构页面大小，因为我们假设第一个页面从未映射。
	// 
	// 这应该与编译器中的minZeroPage一致。
	minLegalPointer uintptr = 4096
)

// physPageSize是操作系统物理页面的大小（以字节为单位）。
// 映射和取消映射操作必须以
// physPageSize的倍数进行。
// 
// 必须在
// mallocinit之前由操作系统初始化代码（通常在osinit中）设置。
var physPageSize uintptr

// physHugePageSize是操作系统默认的物理巨型
// 页面大小，其分配对应用程序是不透明的，以字节为单位。假设
// 并验证为二的幂。
// 
// 如果已设置，则必须在
// mallocinit之前通过操作系统初始化代码（通常在osinit中）进行设置。但是，设置它是可选的，保留默认的
// 值总是安全的（尽管可能效率较低）。
// 
// 由于physHugePageSize总是假定为二的幂，因此physHugePageShift定义为physHugePageSize==1<<physHugePageShift。
// physHugePageShift的目的是避免在性能关键型函数中进行分割。
var (
	physHugePageSize  uintptr
	physHugePageShift uint
)

// OS内存管理抽象层
// 
// 运行时管理的地址空间区域在任何给定时间都可能处于四种
// 状态之一：
// 1）无-未保留和未映射，任何区域的默认状态。
// 2）保留-由运行时拥有，但访问它会导致错误。
// 不计入进程的内存占用。
// 3）Prepared-保留，不由物理内存支持（尽管操作系统可能会延迟实现）。可以有效地过渡到
// 就绪。在这样的区域中访问内存是未定义的（可能是
// 错误，可能返回意外的零等）。
// 4）就绪-可以安全访问。
// 
// 这组状态对于支持当前支持的所有
// 平台来说是绝对必要的。一个人可以一个也没有，保留和
// 准备好。但是，准备状态为我们提供了性能
// /用途的灵活性。例如，在POSIX-y操作系统上，Reserved通常是一个设置了PROT_NONE的
// 私有匿名mmap'd区域，要将
// 转换为Ready需要设置PROT_READ | PROT_WRITE。然而，
// Prepared的规格不足使我们可以使用MADV_FREE从
// Ready to Prepared过渡。因此，使用Prepared state，我们可以在早期设置权限
// 位一次，我们可以有效地告诉操作系统，当我们不需要页面时，可以自由地从我们这里拿走页面。
// 
// 对于每个操作系统，都定义了一组通用的帮助程序，用于在这些状态之间转换
// 内存区域。帮助程序如下：
// 
// sysAlloc将操作系统选择的内存区域从无转换为就绪。
// 更具体地说，它从
// 操作系统获取大量的零内存，通常大约为100千字节或1兆字节。此内存始终可立即使用。
// 
// sysFree将内存区域从任何状态转换为无状态。因此，它是
// 无条件返回内存。如果在分配过程中检测到内存不足错误
// 或在地址空间中划出
// 的对齐部分，则使用此函数。只有当sysReserve始终
// 返回一个与堆分配器的对齐
// 限制对齐的内存区域时，如果sysFree为no op，则没有问题。
// 
// sysReserve将内存区域从无转换为保留。它保留
// 地址空间，这样在访问
// （通过权限或不提交内存）时会导致致命错误。这样的保留是
// 因此从来没有物理内存支持。
// 如果传递给它的指针为非nil，则调用方希望在那里保留
// 保留，但如果该位置不可用，sysReserve仍然可以选择另一个
// 位置。
// 注意：sysReserve返回操作系统对齐的内存，但堆分配器
// 可能使用更大的对齐，因此调用方必须小心重新对齐sysReserve获得的
// 内存。
// 
// sysMap将内存区域从保留转换为准备。它确保
// 内存区域可以有效地转换为就绪状态。
// 
// sysUsed将内存区域从准备状态转换为就绪状态。它通知
// 操作系统需要内存区域，并确保可以安全访问区域
// 。对于没有明确的提交步骤和严格的提交限制的系统，这通常是不可操作的，但对于例如
// Windows来说，这是至关重要的。
// 
// sysUnused将内存区域从就绪转换为准备就绪。它通知
// 操作系统，支持此内存区域的物理页已不再需要，可以重新用于其他目的。
// 系统未使用内存区域的内容将被视为无效，在调用sysUsed之前，不得再次访问该区域。
// 
// sysFault将内存区域从就绪或准备状态转换为保留状态。它
// 标记了一个区域，这样，如果访问该区域，它将始终出现故障。仅用于
// 调试运行时。

func mallocinit() {
	if class_to_size[_TinySizeClass] != _TinySize {
		throw("bad TinySizeClass")
	}

	testdefersizes()

	if heapArenaBitmapBytes&(heapArenaBitmapBytes-1) != 0 {
		// heapBits希望位图上的模块化算法
		// 地址可以工作。
		throw("heapArenaBitmapBytes not a power of 2")
	}

	// 复制统计表的类大小。
	for i := range class_to_size {
		memstats.by_size[i].size = uint32(class_to_size[i])
	}

	// 检查physPageSize。
	if physPageSize == 0 {
		// 操作系统初始化代码无法获取物理页面大小。
		throw("failed to get system page size")
	}
	if physPageSize > maxPhysPageSize {
		print("system page size (", physPageSize, ") is larger than maximum page size (", maxPhysPageSize, ")\n")
		throw("bad system page size")
	}
	if physPageSize < minPhysPageSize {
		print("system page size (", physPageSize, ") is smaller than minimum page size (", minPhysPageSize, ")\n")
		throw("bad system page size")
	}
	if physPageSize&(physPageSize-1) != 0 {
		print("system page size (", physPageSize, ") must be a power of 2\n")
		throw("bad system page size")
	}
	if physHugePageSize&(physHugePageSize-1) != 0 {
		print("system huge page size (", physHugePageSize, ") must be a power of 2\n")
		throw("bad system huge page size")
	}
	if physHugePageSize > maxPhysHugePageSize {
		// physHugePageSize大于支持的最大页面大小。
		// 不要像在其他情况下一样在这里抛出，因为系统以这种方式配置
		// 并没有错，我们只是没有支持它们的代码。
		// 相反，将巨大的页面大小静默设置为零。
		physHugePageSize = 0
	}
	if physHugePageSize != 0 {
		// 由于physHugePageSize是2的幂，因此将physHugePageShift增加到1就足够了。
		for 1<<physHugePageShift != physHugePageSize {
			physHugePageShift++
		}
	}
	if pagesPerArena%pagesPerSpanRoot != 0 {
		print("pagesPerArena (", pagesPerArena, ") is not divisible by pagesPerSpanRoot (", pagesPerSpanRoot, ")\n")
		throw("bad pagesPerSpanRoot")
	}
	if pagesPerArena%pagesPerReclaimerChunk != 0 {
		print("pagesPerArena (", pagesPerArena, ") is not divisible by pagesPerReclaimerChunk (", pagesPerReclaimerChunk, ")\n")
		throw("bad pagesPerReclaimerChunk")
	}

	// 初始化堆。
	mheap_.init()
	mcache0 = allocmcache()
	lockInit(&gcBitsArenas.lock, lockRankGcBitsArenas)
	lockInit(&proflock, lockRankProf)
	lockInit(&globalAlloc.mutex, lockRankGlobalAlloc)

	// 创建初始竞技场增长提示。
	if sys.PtrSize == 8 {
		// 在64位机器上，我们选择以下提示
		// 因为：
		// 
		// 1。从地址空间的中间开始
		// 可以更容易地扩展出一个连续范围
		// 而无需运行到其他映射。中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网。这使得Go堆地址在调试时更容易识别。中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网中国英语学习网。gccgo中的堆栈扫描仍然是保守的，
		// 因此地址与其他数据的区别非常重要。
		// 
		// 从0x00c0开始表示有效内存地址
		// 将从0x00c0、0x00c1、。。。
		// 在little endian中，是C000，c1 00。。。这些都不是有效的
		// UTF-8序列，否则它们将尽可能远离
		// ff（可能是一个公共字节）。如果失败，我们将尝试其他0xXXc0 
		// 地址。先前尝试使用0x11f8时，在线程分配期间导致OS X上出现内存不足错误
		// 。0x00c0导致与
		// AddressSanitarizer冲突，该地址Sanitarizer将保留0x0100之前的所有内存。
		// 这些选择降低了保守的垃圾收集器
		// 不收集内存的可能性，因为某些非指针内存块
		// 具有与内存地址匹配的位模式。
		// 
		// 然而，在arm64上，我们忽略了上述所有建议，并将
		// 分配设置为0x40<<32，因为当使用带有3级
		// 转换缓冲区的4k页面时，ios/arm64上的用户地址空间限制为39位
		// ，地址空间甚至更小。
		// 
		// 在AIX上，64位的MMAP从0x0A00000000000000开始。
		// 进程。
		for i := 0x7f; i >= 0; i-- {
			var p uintptr
			switch {
			case raceenabled:
				// TSAN运行时要求堆
				// 在范围内[0x00c000000000，
				// 0x00e000000000）.
				p = uintptr(i)<<32 | uintptrMask&(0x00c0<<32)
				if p >= uintptrMask&0x00e000000000 {
					continue
				}
			case GOARCH == "arm64" && GOOS == "ios":
				p = uintptr(i)<<40 | uintptrMask&(0x0013<<28)
			case GOARCH == "arm64":
				p = uintptr(i)<<40 | uintptrMask&(0x0040<<32)
			case GOOS == "aix":
				if i == 0 {
					// 我们不直接在0x0A000000000之后使用地址
					// 以避免非go程序与其他MMAP发生冲突。
					continue
				}
				p = uintptr(i)<<40 | uintptrMask&(0xa0<<52)
			default:
				p = uintptr(i)<<40 | uintptrMask&(0x00c0<<32)
			}
			hint := (*arenaHint)(mheap_.arenaHintAlloc.alloc())
			hint.addr = p
			hint.next, mheap_.arenaHints = mheap_.arenaHints, hint
		}
	} else {
		// 在32位机器上，我们更关心
		// 如何保持可用堆的连续性us.
		// 因此：
		// 
		// 1.我们为前面的所有堆预留空间，所以
		// 它们不会与堆交错。它们是
		// /~258MB，所以这并不太糟糕。（如果这是
		// 问题，我们可以在前端预留较小的空间。）
		// 
		// 2.我们提示堆从二进制文件
		// 结尾的正上方开始，因此我们有最好的机会保持它的连续性。
		// 
		// 3.我们试图确定一个相当大的初始
		// 堆保留。

		const arenaMetaSize = (1 << arenaBits) * unsafe.Sizeof(heapArena{})
		meta := uintptr(sysReserve(nil, arenaMetaSize))
		if meta != 0 {
			mheap_.heapArenaAlloc.init(meta, arenaMetaSize, true)
		}

		// 我们想把竞技场放低一点，但是如果我们把
		// 和C代码联系起来，那么可能是全局构造函数
		// 调用了malloc并调整了进程'brk.
		// 查询brk，这样我们就可以避免在它上面映射
		// 区域（这将导致内核将
		// 区域放在其他地方，可能位于高
		// 地址）.
		procBrk := sbrk0()

		// 如果我们要求数据段的结尾，但操作系统需要更多的空间
		// 在我们开始分配之前，它将发出一个稍高的指针。除了QEMU，它是
		// bug，与往常一样：它不会调整指针r 
		// 向上。因此，我们自己将其向上调整一点：
		// 1/4 MB以远离正在运行的二进制映像。
		p := firstmoduledata.end
		if p < procBrk {
			p = procBrk
		}
		if mheap_.heapArenaAlloc.next <= p && p < mheap_.heapArenaAlloc.end {
			p = mheap_.heapArenaAlloc.end
		}
		p = alignUp(p+(256<<10), heapArenaBytes)
		// 因为我们担心
		// 32位上的碎片，所以我们尝试进行较大的初始保留。
		arenaSizes := []uintptr{
			512 << 20,
			256 << 20,
			128 << 20,
		}
		for _, arenaSize := range arenaSizes {
			a, size := sysReserveAligned(unsafe.Pointer(p), arenaSize, heapArenaBytes)
			if a != nil {
				mheap_.arena.init(uintptr(a), size, false)
				p = mheap_.arena.end // 获取下面的提示
				break
			}
		}
		hint := (*arenaHint)(mheap_.arenaHintAlloc.alloc())
		hint.addr = p
		hint.next, mheap_.arenaHints = mheap_.arenaHints, hint
	}
}

// sysAlloc为至少n个字节分配堆竞技场空间。返回的指针始终是由
// h.arenas元数据对齐和支持的heapArenaBytes。返回的大小始终是
// heapArenaBytes的倍数。sysAlloc在失败时返回nil。
// 没有相应的空闲函数ion.
// 
// sysAlloc返回一个处于保留状态的内存区域。该区域必须转换为已准备好，然后在使用前准备好。
// 
// h必须被锁定。
func (h *mheap) sysAlloc(n uintptr) (v unsafe.Pointer, size uintptr) {
	assertLockHeld(&h.lock)

	n = alignUp(n, heapArenaBytes)

	// 首先，尝试竞技场预保留。
	v = h.arena.alloc(n, heapArenaBytes, &memstats.heap_sys)
	if v != nil {
		size = n
		goto mapped
	}

	// 尝试增长内存堆在一个提示地址。
	for h.arenaHints != nil {
		hint := h.arenaHints
		p := hint.addr
		if hint.down {
			p -= n
		}
		if p+n < p {
			// 我们不能使用这个，所以不要问。
			v = nil
		} else if arenaIndex(p+n-1) >= 1<<arenaBits {
			// 在可寻址堆之外。不能使用。
			v = nil
		} else {
			v = sysReserve(unsafe.Pointer(p), n)
		}
		if p == uintptr(v) {
			// 成功。更新提示。
			if !hint.down {
				p += n
			}
			hint.addr = p
			size = n
			break
		}
		// 失败。放弃这个提示，然后尝试下一个。
		// 
		// TODO:如果可以使用sysReserve，这将更干净CDEFG
		// 中，这已经是Windows的行为方式，因此
		// 它将简化那里的事情。
		if v != nil {
			sysFree(v, n, nil)
		}
		h.arenaHints = hint.next
		h.arenaHintAlloc.free(unsafe.Pointer(hint))
	}

	if size == 0 {
		if raceenabled {
			// 竞赛检测器假定堆位于
			// /[0x00c000000000，0x00e000000000），但我们在这个区域的提示已经用完了。给我们一个不错的失败。所有提示都失败了，所以我们将使用任何提示
			throw("too many address space collisions for -race mode")
		}

		// 我们。
		v, size = sysReserveAligned(nil, n, heapArenaBytes)
		if v == nil {
			return nil, 0
		}

		// 创建扩展此区域的新提示。
		hint := (*arenaHint)(h.arenaHintAlloc.alloc())
		hint.addr, hint.down = uintptr(v), true
		hint.next, mheap_.arenaHints = mheap_.arenaHints, hint
		hint = (*arenaHint)(h.arenaHintAlloc.alloc())
		hint.addr = uintptr(v) + size
		hint.next, mheap_.arenaHints = mheap_.arenaHints, hint
	}

	// 检查错误指针或我们不能使用的指针。
	{
		var bad string
		p := uintptr(v)
		if p+size < p {
			bad = "region exceeds uintptr range"
		} else if arenaIndex(p) >= 1<<arenaBits {
			bad = "base outside usable address space"
		} else if arenaIndex(p+size-1) >= 1<<arenaBits {
			bad = "end outside usable address space"
		}
		if bad != "" {
			// 这在大多数体系结构上都是不可能的，
			// 但调试时会非常混乱。
			print("runtime: memory allocated by OS [", hex(p), ", ", hex(p+size), ") not in usable address space: ", bad, "\n")
			throw("memory reservation exceeds address space limit")
		}
	}

	if uintptr(v)&(heapArenaBytes-1) != 0 {
		throw("misrounded allocation in sysAlloc")
	}

mapped:
	// 创建竞技场元数据。
	for ri := arenaIndex(uintptr(v)); ri <= arenaIndex(uintptr(v)+size-1); ri++ {
		l2 := h.arenas[ri.l1()]
		if l2 == nil {
			// 分配二级竞技场地图。
			l2 = (*[1 << arenaL2Bits]*heapArena)(persistentalloc(unsafe.Sizeof(*l2), sys.PtrSize, nil))
			if l2 == nil {
				throw("out of memory allocating heap arena map")
			}
			atomic.StorepNoWB(unsafe.Pointer(&h.arenas[ri.l1()]), unsafe.Pointer(l2))
		}

		if l2[ri.l2()] != nil {
			throw("arena already initialized")
		}
		var r *heapArena
		r = (*heapArena)(h.heapArenaAlloc.alloc(unsafe.Sizeof(*r), sys.PtrSize, &memstats.gcMiscSys))
		if r == nil {
			r = (*heapArena)(persistentalloc(unsafe.Sizeof(*r), sys.PtrSize, &memstats.gcMiscSys))
			if r == nil {
				throw("out of memory allocating heap arena metadata")
			}
		}

		// 将竞技场添加到竞技场列表。
		if len(h.allArenas) == cap(h.allArenas) {
			size := 2 * uintptr(cap(h.allArenas)) * sys.PtrSize
			if size == 0 {
				size = physPageSize
			}
			newArray := (*notInHeap)(persistentalloc(size, sys.PtrSize, &memstats.gcMiscSys))
			if newArray == nil {
				throw("out of memory allocating allArenas")
			}
			oldSlice := h.allArenas
			*(*notInHeapSlice)(unsafe.Pointer(&h.allArenas)) = notInHeapSlice{newArray, len(h.allArenas), int(size / sys.PtrSize)}
			copy(h.allArenas, oldSlice)
			// 不要释放旧的备份数组，因为
			// 可能存在并发读卡器。因为我们
			// 每次将阵列加倍，这可能导致
			// 最多浪费2倍。
		}
		h.allArenas = h.allArenas[:len(h.allArenas)+1]
		h.allArenas[len(h.allArenas)-1] = ri

		// 以原子方式存储，以防在释放堆锁
		// 之前，新堆竞技场中的对象可见（这不应该发生，但
		// 没有什么缺点）。
		atomic.StorepNoWB(unsafe.Pointer(&l2[ri.l2()]), unsafe.Pointer(r))
	}

	// 告诉竞争检测器有关新堆内存的信息。
	if raceenabled {
		racemapshadow(v, size)
	}

	return
}

// sysreservealligned类似于sysReserve，但返回的指针是
// aligned to align bytes。它可以保留n个或n+align字节，
// 因此返回保留的大小。
func sysReserveAligned(v unsafe.Pointer, size, align uintptr) (unsafe.Pointer, uintptr) {
	// 由于在使用此
	// 函数时对齐度相当大，我们不太可能偶然获得它，因此我们要求
	// 获得更大的区域，并删除我们不需要的部分。
	retries := 0
retry:
	p := uintptr(sysReserve(v, size+align))
	switch {
	case p == 0:
		return nil, 0
	case p&(align-1) == 0:
		// 我们很幸运，得到了一个对齐的区域，所以我们可以使用整个东西。
		return unsafe.Pointer(p), size + align
	case GOOS == "windows":
		// 在Windows上，我们无法释放
		// 保留的部分，因此我们释放整个内容，并重新保留对齐的子区域。这可能是比赛，所以我们可能要再试一次。
		sysFree(unsafe.Pointer(p), size+align, nil)
		p = alignUp(p, align)
		p2 := sysReserve(unsafe.Pointer(p), size)
		if p != uintptr(p2) {
			// 一定是参加了比赛。再试一次。
			sysFree(p2, size, nil)
			if retries++; retries == 100 {
				throw("failed to allocate aligned heap memory; too many retries")
			}
			goto retry
		}
		// 成功。
		return p2, size
	default:
		// 修剪未对齐的零件。
		pAligned := alignUp(p, align)
		sysFree(unsafe.Pointer(p), pAligned-p, nil)
		end := pAligned + size
		endLen := (p + size + align) - end
		if endLen > 0 {
			sysFree(unsafe.Pointer(end), endLen, nil)
		}
		return unsafe.Pointer(pAligned), size
	}
}

// 所有0字节分配的基址
var zerobase uintptr

// 如果下一个可用对象很快可用，nextFreeFast将返回下一个可用对象。
// 否则返回0。
func nextFreeFast(s *mspan) gclinkptr {
	theBit := sys.Ctz64(s.allocCache) // allocCache中是否有自由对象？
	if theBit < 64 {
		result := s.freeindex + uintptr(theBit)
		if result < s.nelems {
			freeidx := result + 1
			if freeidx%64 == 0 && freeidx != s.nelems {
				return 0
			}
			s.allocCache >>= uint(theBit + 1)
			s.freeindex = freeidx
			s.allocCount++
			return gclinkptr(result*s.elemsize + s.base())
		}
	}
	return 0
}

// nextFree返回缓存范围中的下一个可用对象（如果可用）。
// 否则，它将使用一个可用对象的范围重新填充缓存，并且返回该对象以及一个标志，表明这是一个沉重的
// 权重分配。如果是重量级分配，调用方必须确定是否需要启动新的GC循环或GC是否处于活动状态
// 此GOROUTE是否需要协助GC。
// 
// 必须在不可抢占的上下文中运行，否则
// c的所有者可能会更改。
func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, shouldhelpgc bool) {
	s = c.alloc[spc]
	shouldhelpgc = false
	freeIndex := s.nextFreeIndex()
	if freeIndex == s.nelems {
		// 跨度已满。
		if uintptr(s.allocCount) != s.nelems {
			println("runtime: s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
			throw("s.allocCount != s.nelems && freeIndex == s.nelems")
		}
		c.refill(spc)
		shouldhelpgc = true
		s = c.alloc[spc]

		freeIndex = s.nextFreeIndex()
	}

	if freeIndex >= s.nelems {
		throw("freeIndex is not valid")
	}

	v = gclinkptr(freeIndex*s.elemsize + s.base())
	s.allocCount++
	if uintptr(s.allocCount) > s.nelems {
		println("s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
		throw("s.allocCount > s.nelems")
	}
	return
}

// 分配一个字节大小的对象。
// 从per-P缓存的空闲列表中分配小对象。
// 直接从堆中分配大对象（>32KB）。
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
	if gcphase == _GCmarktermination {
		throw("mallocgc called with gcphase == _GCmarktermination")
	}

	if size == 0 {
		return unsafe.Pointer(&zerobase)
	}

	if debug.malloc {
		if debug.sbrk != 0 {
			align := uintptr(16)
			if typ != nil {
				// TODO（奥斯汀）：这应该只是
				// align=uintptpr（typ.align）
				// 但在32位平台上只有4，即使typ中有uint64字段（请参见#599）。
				// 这会导致64位原子访问死机。
				// 因此，我们使用更严格的对齐方式，以更好地匹配正常分配器。
				if size&7 == 0 {
					align = 8
				} else if size&3 == 0 {
					align = 4
				} else if size&1 == 0 {
					align = 2
				} else {
					align = 1
				}
			}
			return persistentalloc(size, align, &memstats.other_sys)
		}

		if inittrace.active && inittrace.id == getg().goid {
			// 初始化函数在一个goroutine中顺序执行。
			inittrace.allocs += 1
		}
	}

	// assistG是为此分配收取的费用，如果
	// GC当前不活动，则为零。
	var assistG *g
	if gcBlackenEnabled != 0 {
		// 为此分配向当前用户G收费。
		assistG = getg()
		if assistG.m.curg != nil {
			assistG = assistG.m.curg
		}
		// 从G中收取分配费用。我们将在mallocgc结束时对内部碎片计算
		// 费用。
		assistG.gcAssistBytes -= int64(size)

		if assistG.gcAssistBytes < 0 {
			// 这个G负债了。分配前，协助总承包商纠正
			// 此错误。这必须在禁用抢占之前发生。
			gcAssistAlloc(assistG)
		}
	}

	// 将mp.mallocing设置为不被GC抢占。
	mp := acquirem()
	if mp.mallocing != 0 {
		throw("malloc deadlock")
	}
	if mp.gsignal == getg() {
		throw("malloc during signal")
	}
	mp.mallocing = 1

	shouldhelpgc := false
	dataSize := size
	c := getMCache()
	if c == nil {
		throw("mallocgc called without a P or outside bootstrapping")
	}
	var span *mspan
	var x unsafe.Pointer
	noscan := typ == nil || typ.ptrdata == 0
	// 在某些情况下，块归零可以有利地（为了减少延迟）
	// 延迟到可以抢占时；isZeroed跟踪该状态。
	isZeroed := true
	if size <= maxSmallSize {
		if noscan && size < maxTinySize {
			// 微型分配器。
			// 
			// 微型分配器将多个微型分配请求
			// 组合到单个内存块中。当所有子对象都不可访问时，生成的内存块
			// 将被释放。子对象
			// 必须是noscan（没有指针），这确保了
			// 潜在的内存浪费量是有限的。
			// 
			// 用于组合（maxTinySize）的内存块大小可调。
			// 当前设置为16字节，这与2倍最坏情况下的内存
			// 损耗有关（当除一个子对象之外的所有子对象都无法访问时）。
			// 8字节完全不会造成浪费，但提供的合并机会较少。
			// 32字节提供了更多的组合机会，
			// 但可能导致4倍的最坏情况损耗。
			// 无论区块大小，最佳获胜案例为8x。
			// 
			// 不能显式释放从微小分配器获取的对象。
			// 因此，当显式释放对象时，我们确保
			// 其大小>=maxTinySize。
			// 
			// SetFinalizer对于可能来自微型分配器的对象有一个特殊情况，在这种情况下，它允许为内存块的内部字节设置终结器
			// 。
			// 
			// tiny分配器的主要目标是小字符串和
			// 独立转义变量。在json基准上
			// 分配器将分配数减少约12%，
			// 将堆大小减少约20%。
			off := c.tinyoffset
			// 对齐所需（保守）对齐的小指针。
			if size&7 == 0 {
				off = alignUp(off, 8)
			} else if sys.PtrSize == 4 && size == 12 {
				// 在32位
				// 系统上，将12个字节的对象保守地对齐到8个字节，这样，第一个字段是64位
				// 值的对象将对齐到8个字节，并且不会在
				// 原子访问上导致故障。见第37262期。
				// TODO（mknyszek）：如果问题36606 
				// 得到解决，请删除此解决方法。
				off = alignUp(off, 8)
			} else if size&3 == 0 {
				off = alignUp(off, 4)
			} else if size&1 == 0 {
				off = alignUp(off, 2)
			}
			if off+size <= maxTinySize && c.tiny != 0 {
				// 对象适合现有的小块。
				x = unsafe.Pointer(c.tiny + off)
				c.tinyoffset = off + size
				c.tinyAllocs++
				mp.mallocing = 0
				releasem(mp)
				return x
			}
			// 分配一个新的maxTinySize块。
			span = c.alloc[tinySpanClass]
			v := nextFreeFast(span)
			if v == 0 {
				v, span, shouldhelpgc = c.nextFree(tinySpanClass)
			}
			x = unsafe.Pointer(v)
			(*[2]uint64)(x)[0] = 0
			(*[2]uint64)(x)[1] = 0
			// 根据剩余的可用空间量，查看是否需要用新的块替换现有的小块。
			if !raceenabled && (size < c.tinyoffset || c.tiny == 0) {
				// 注意：种族检测器打开时禁用，请参阅此函数末尾附近的注释。
				c.tiny = uintptr(x)
				c.tinyoffset = size
			}
			size = maxTinySize
		} else {
			var sizeclass uint8
			if size <= smallSizeMax-8 {
				sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)]
			} else {
				sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]
			}
			size = uintptr(class_to_size[sizeclass])
			spc := makeSpanClass(sizeclass, noscan)
			span = c.alloc[spc]
			v := nextFreeFast(span)
			if v == 0 {
				v, span, shouldhelpgc = c.nextFree(spc)
			}
			x = unsafe.Pointer(v)
			if needzero && span.needzero != 0 {
				memclrNoHeapPointers(unsafe.Pointer(v), size)
			}
		}
	} else {
		shouldhelpgc = true
		// 对于大型分配，请跟踪归零状态，以便
		// 稍后可以在可抢占上下文中进行批量归零。
		span, isZeroed = c.allocLarge(size, needzero && !noscan, noscan)
		span.freeindex = 1
		span.allocCount = 1
		x = unsafe.Pointer(span.base())
		size = span.elemsize
	}

	var scanSize uintptr
	if !noscan {
		// 如果分配一个defer+arg块，现在我们已经选择了一个malloc大小的
		// 足够容纳所有内容，请将“asquid for”大小减少到
		// 只包含defer头，这样GC位图将记录arg块
		// 为完全不包含任何内容（好像它是
		// 大小舍入导致的malloc块末尾未使用的空间）.
		// 延迟参数区域作为扫描堆栈的一部分进行扫描。
		if typ == deferType {
			dataSize = unsafe.Sizeof(_defer{})
		}
		heapBitsSetType(uintptr(x), size, dataSize, typ)
		if dataSize > typ.size {
			// 数组分配。如果有任何
			// 指针，GC必须扫描到最后一个
			// 元素。
			if typ.ptrdata != 0 {
				scanSize = dataSize - typ.size + typ.ptrdata
			}
		} else {
			scanSize = typ.ptrdata
		}
		c.scanAlloc += scanSize
	}

	// 确保上面的存储将x初始化为
	// 键入安全内存并设置堆位出现在
	// 调用方可以使垃圾
	// 收集器可以观察到x之前。否则，在弱顺序机器上，
	// 垃圾收集器可以跟随指向x的指针，
	// 但可以看到未初始化的内存或陈旧的堆位。
	publicationBarrier()

	// 分配GC期间为黑色。
	// 所有插槽均为零，因此无需扫描。
	// 这可能与GC竞争，因此，如果存在标记位的竞争，请以原子方式执行。
	// 注意，缓存c仅在获取m时有效；请参阅#47302 
	if gcphase != _GCoff {
		gcmarknewobject(span, uintptr(x), size, scanSize)
	}

	if raceenabled {
		racemalloc(x, size)
	}

	if msanenabled {
		msanmalloc(x, size)
	}

	if rate := MemProfileRate; rate > 0 {
		if rate != 1 && size < c.nextSample {
			c.nextSample -= size
		} else {
			profilealloc(mp, x, size)
		}
	}
	mp.mallocing = 0
	releasem(mp)

	// 无指针数据可以归零在可能发生抢占的情况下，延迟。
	// x将保持内存的活动状态。
	if !isZeroed && needzero {
		memclrNoHeapPointersChunked(size, x) // 这是一个可能的抢占点：请参阅#47302 
	}

	if debug.malloc {
		if debug.allocfreetrace != 0 {
			tracealloc(x, size, typ)
		}

		if inittrace.active && inittrace.id == getg().goid {
			// Init函数在单个goroutine中顺序执行。
			inittrace.bytes += uint64(size)
		}
	}

	if assistG != nil {
		// 说明辅助
		assistG.gcAssistBytes -= int64(size - dataSize)
	}

	if shouldhelpgc {
		if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
			gcStart(t)
		}
	}

	if raceenabled && noscan && dataSize < maxTinySize {
		// Pad tinysize分配，使其与tinyalloc区域的末端
		// 对齐。这确保了任何超出对象顶端的算术
		// 都可以通过checkptr检测到
		// （问题38872）.
		// 请注意，我们在启用race时禁用tinyalloc，以使其正常工作。
		// TODO:此填充仅在启用竞赛检测器
		// 时执行。如果任何包
		// 都是使用checkptr编译的，则最好启用它，但没有简单的方法可以实现
		// 检测（尤其是在编译时）.
		// TODO:为所有分配启用此填充，而不仅仅是
		// tinyalloc分配。这很棘手，因为指针映射。
		// 可能只是所有noscan对象？
		x = add(x, size-dataSize)
	}

	return x
}

// memclrNoHeapPointersChunked反复调用memclrNoHeapPointers 
// 归零，有抢占的机会
// 一路走来。memclrNoHeapPointers不包含安全点，而且
// 无法抢占计划，因此这提供了一个仍然有效的
// 块拷贝，也可以在合理的粒度上抢占。
// 
// 小心使用；如果正在清除的数据被标记为包含
// 指针，则允许GC在全部清除之前运行。
func memclrNoHeapPointersChunked(size uintptr, x unsafe.Pointer) {
	v := uintptr(x)
	// 这是通过基准测试得到的。128k太小，512k太大。
	const chunkBytes = 256 * 1024
	vsize := v + size
	for voff := v; voff < vsize; voff = voff + chunkBytes {
		if getg().preempt {
			// 可能持有锁，例如，分析
			goschedguarded()
		}
		// 清除最小（可用，整块）字节
		n := vsize - voff
		if n > chunkBytes {
			n = chunkBytes
		}
		memclrNoHeapPointers(unsafe.Pointer(voff), n)
	}
}

// 实现新内置
// 编译器（前端和SSA后端）知道此函数的签名
// 
func newobject(typ *_type) unsafe.Pointer {
	return mallocgc(typ.size, typ, true)
}

// go:linkname reflect\u unsafe\u New reflect.unsafe\u New 
func reflect_unsafe_New(typ *_type) unsafe.Pointer {
	return mallocgc(typ.size, typ, true)
}

// go:linkname reflectlite\u unsafe\u New internal/reflectlite.unsafe\u New 
func reflectlite_unsafe_New(typ *_type) unsafe.Pointer {
	return mallocgc(typ.size, typ, true)
}

// newarray分配类型为n的元素数组。
func newarray(typ *_type, n int) unsafe.Pointer {
	if n == 1 {
		return mallocgc(typ.size, typ, true)
	}
	mem, overflow := math.MulUintptr(typ.size, uintptr(n))
	if overflow || mem > maxAlloc || n < 0 {
		panic(plainError("runtime: allocation size out of range"))
	}
	return mallocgc(mem, typ, true)
}

// go:linkname reflect\u unsafe\u NewArray reflect.unsafe\u NewArray 
func reflect_unsafe_NewArray(typ *_type, n int) unsafe.Pointer {
	return newarray(typ, n)
}

func profilealloc(mp *m, x unsafe.Pointer, size uintptr) {
	c := getMCache()
	if c == nil {
		throw("profilealloc called without a P or outside bootstrapping")
	}
	c.nextSample = nextSample()
	mProf_Malloc(x, size)
}

// nextSample返回堆分析的下一个采样点。目标是平均每个MemProfileRate字节对分配进行采样，但在分配时间线上完全随机分布；此
// 对应于参数为MemProfileRate的泊松过程。在Poisson-
// 过程中，两个样本之间的距离遵循指数分布（exp（MemProfileRate）），因此最佳返回值是从平均值为MemProfileRate的指数分布中获取的随机
// 数。
func nextSample() uintptr {
	if MemProfileRate == 1 {
		// 调用者将我们的返回值分配给
		// mcache.next_sample，但当速率为1时，不会使用next_sample。因此，避免下面的数学运算，只需返回一些内容。
		return 0
	}
	if GOOS == "plan9" {
		// 计划9不支持注释处理程序中的浮点。
		if g := getg(); g == g.m.gsignal {
			return nextSampleNoFP()
		}
	}

	return uintptr(fastexprand(MemProfileRate))
}

// fastexprand返回指数分布中的随机数，
// 指定的平均值。
func fastexprand(mean int) int32 {
	// 避免溢出。最大可能步长为
	// /-ln（1/（1<<randomBitCount））*平均值，约为20*平均值。
	switch {
	case mean > 0x7000000:
		mean = 0x7000000
	case mean == 0:
		return 0
	}

	// 取指数分布exp（-mean*x）的随机样本。概率分布函数是mean*exp（-mean*x），所以CDF是mean*exp
	// q=1-p==exp（-mean*x）
	// log_e（q）=mean*x 
	// log_e（q）/mean=x 
	// x=-log_e（q）*平均
	// x=log_2（q）*（-log_e（2））*平均值；使用log_2提高效率
	const randomBitCount = 26
	q := fastrand()%(1<<randomBitCount) + 1
	qlog := fastlog2(float64(q)) - randomBitCount
	if qlog > 0 {
		qlog = 0
	}
	const minusLog2 = -0.6931471805599453 // /-ln（2）
	return int32(qlog*(minusLog2*float64(mean))) + 1
}

// nextSampleNoFP与nextSample类似，但使用较旧的
// 更简单的代码来避免浮点运算。
func nextSampleNoFP() uintptr {
	// 设置第一次分配样本大小。
	rate := MemProfileRate
	if rate > 0x3fffffff { // make 2*rate not overflow 
		rate = 0x3fffffff
	}
	if rate != 0 {
		return uintptr(fastrand() % uint32(2*rate))
	}
	return 0
}

type persistentAlloc struct {
	base *notInHeap
	off  uintptr
}

var globalAlloc struct {
	mutex
	persistentAlloc
}

// persistentChunkSize是我们在增长
// persistentAlloc时分配的字节数。
const persistentChunkSize = 256 << 10

// persistentChunks是我们已分配的所有持久块的列表。该列表通过
// 持久块中的第一个单词进行维护。这是原子更新的。
var persistentChunks *notInHeap

// 可以分配小块的sysAlloc的包装器。
// 没有关联的自由操作。
// 用于函数/类型/调试相关的持久数据。
// 如果align为0，则使用默认的align（当前为8）。
// 返回的内存将归零。ABCFDG 
func persistentalloc(size, align uintptr, sysStat *sysMemStat) unsafe.Pointer {
	var p *notInHeap
	systemstack(func() {
		p = persistentalloc1(size, align, sysStat)
	})
	return unsafe.Pointer(p)
}

// 必须在系统堆栈上运行，因为堆栈增长可以（重新）调用它。
// 见第9174期。
// go:systemstack 
func persistentalloc1(size, align uintptr, sysStat *sysMemStat) *notInHeap {
	const (
		maxBlock = 64 << 10 // windows上的VM保留粒度为64K 
	)

	if size == 0 {
		throw("persistentalloc: size == 0")
	}
	if align != 0 {
		if align&(align-1) != 0 {
			throw("persistentalloc: align is not a power of 2")
		}
		if align > _PageSize {
			throw("persistentalloc: align is too large")
		}
	} else {
		align = 8
	}

	if size >= maxBlock {
		return (*notInHeap)(sysAlloc(size, sysStat))
	}

	mp := acquirem()
	var persistent *persistentAlloc
	if mp != nil && mp.p != 0 {
		persistent = &mp.p.ptr().palloc
	} else {
		lock(&globalAlloc.mutex)
		persistent = &globalAlloc.persistentAlloc
	}
	persistent.off = alignUp(persistent.off, align)
	if persistent.off+size > persistentChunkSize || persistent.base == nil {
		persistent.base = (*notInHeap)(sysAlloc(persistentChunkSize, &memstats.other_sys))
		if persistent.base == nil {
			if persistent == &globalAlloc.persistentAlloc {
				unlock(&globalAlloc.mutex)
			}
			throw("runtime: cannot allocate memory")
		}

		// 将新块添加到persistentChunks列表中。
		for {
			chunks := uintptr(unsafe.Pointer(persistentChunks))
			*(*uintptr)(unsafe.Pointer(persistent.base)) = chunks
			if atomic.Casuintptr((*uintptr)(unsafe.Pointer(&persistentChunks)), chunks, uintptr(unsafe.Pointer(persistent.base))) {
				break
			}
		}
		persistent.off = alignUp(sys.PtrSize, align)
	}
	p := persistent.base.add(persistent.off)
	persistent.off += size
	releasem(mp)
	if persistent == &globalAlloc.persistentAlloc {
		unlock(&globalAlloc.mutex)
	}

	if sysStat != &memstats.other_sys {
		sysStat.add(int64(size))
		memstats.other_sys.add(-int64(size))
	}
	return p
}

// inPersistentAlloc报告p是否指向由
// persistentalloc分配的内存。这必须是nosplit，因为它由
// cgo检查代码调用，该代码由写屏障代码调用。
// go:nosplit 
func inPersistentAlloc(p uintptr) bool {
	chunk := atomic.Loaduintptr((*uintptr)(unsafe.Pointer(&persistentChunks)))
	for chunk != 0 {
		if p >= chunk && p < chunk+persistentChunkSize {
			return true
		}
		chunk = *(*uintptr)(unsafe.Pointer(chunk))
	}
	return false
}

// linearloc是一个简单的线性分配器，它预先保留一个区域
// 内存，然后根据需要将该区域映射到就绪状态
// 中。
// 
// 调用方负责锁定。
type linearAlloc struct {
	next   uintptr // 下一个可用字节
	mapped uintptr // 映射空间结束后一个字节
	end    uintptr // 保留空间结束

	mapMemory bool // 如果为真，则将内存从保留转换为就绪
}

func (l *linearAlloc) init(base, size uintptr, mapMemory bool) {
	if base+size < base {
		// 切掉最后一个字节。运行时没有准备好处理边界可能溢出的情况。
		// 保留内存，所以我们不映射它
		// /以后。
		size -= 1
	}
	l.next, l.mapped = base, base
	l.end = base + size
	l.mapMemory = mapMemory
}

func (l *linearAlloc) alloc(size, align uintptr, sysStat *sysMemStat) unsafe.Pointer {
	p := alignUp(l.next, align)
	if p+size > l.end {
		return nil
	}
	l.next = p + size
	if pEnd := alignUp(l.next-1, physPageSize); pEnd > l.mapped {
		if l.mapMemory {
			// 从保留到准备到就绪的转换。
			sysMap(unsafe.Pointer(l.mapped), pEnd-l.mapped, sysStat)
			sysUsed(unsafe.Pointer(l.mapped), pEnd-l.mapped)
		}
		l.mapped = pEnd
	}
	return unsafe.Pointer(p)
}

// notInHeap是由较低级别的分配器分配的堆外内存，如sysAlloc或persistentAlloc。
// 
// 一般来说，最好使用标记为go:notinheap，
// 的实类型，但对于不可能的情况（如在分配器中），这是一个泛型。
// 
// TODO:是否将其用作sysAlloc、persistentAlloc等的返回类型？
// 
// go:notinheap 
type notInHeap struct{}

func (p *notInHeap) add(bytes uintptr) *notInHeap {
	return (*notInHeap)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + bytes))
}
